package spitfire.ksim.evaluation;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

import spitfire.ksim.algorithm.RankList;
import spitfire.ksim.algorithm.RankList.RankRecord;
import spitfire.ksim.node.KGateway;
import spitfire.ksim.node.KNode;
import spitfire.ksim.node.KSensor;
import spitfire.ksim.node.KSensorNode;
import spitfire.ksim.node.RankListRecvListener;
import spitfire.ksim.simulation.KSimulation;
import spitfire.ksim.util.TPoint;


public class EvaluationClass {

	private int groupSize = 1;
	private KSimulation simulation;
	private int timeTick = 60000;
	private int refreshPeriod = 0;
	private ArrayList<NodeInfo> initNodeInfoList = new ArrayList<NodeInfo>();
	private ArrayList<NodeInfo> testNodeInfoList = new ArrayList<NodeInfo>();
	private List<EvalRecord> recordList = new CopyOnWriteArrayList<EvalRecord>();
	private Random rand = new Random();
	private double alpha = 0;
	private long revertPoint; // 
//	private String gatewayAlgo = "Representative";
//	private double ratioSum;
	StringBuilder sb = new StringBuilder();
	
	// gui stuff
	private int row;
	private int col;
	private int width = 40;
	private int height = 40;
	public static final int MAXROW = 10;
	public static final int MAXCOL = 10;
	
	public EvaluationClass(KSimulation sim) {
		simulation = sim;
	}
	
//	public void setGatewayAlgo(String gatewayAlgo) {
//		this.gatewayAlgo = gatewayAlgo;
//	}
//
//	public String getGatewayAlgo() {
//		return gatewayAlgo;
//	}

	public void addInitNode(String npfPath, Map<String, String> typeSdMap) {
		initNodeInfoList.add(new NodeInfo(npfPath, typeSdMap));
	}
	
	public boolean hasInitNode(String npfPath) {
		for (NodeInfo info : initNodeInfoList) {
			if (info.npfPath.equals(npfPath)) {
				return true;
			}
		}
		return false;
	}
	
	public void addTestNode(String npfPath, Map<String, String> typeSdMap) {
		testNodeInfoList.add(new NodeInfo(npfPath, typeSdMap));
	}
	
	public void loadConfig(String path) {
		try {
			BufferedReader br = new BufferedReader(new FileReader(path));
			try {
				String line = br.readLine();
				while (line.equals("") || line.startsWith("#")) {
					// omit empty lines and comment lines 
					line = br.readLine();
				}
				groupSize = Integer.parseInt(line);
				line = br.readLine();
				boolean init = true;
				while ((line = br.readLine()) != null) {
					if (line.equals("") || line.startsWith("#")) {
						// omit empty lines and comment lines 
						continue;
					}
					if (line.equalsIgnoreCase("Init:")) {
						init = true;
						continue;
					} else if (line.equalsIgnoreCase("Test:")) {
						init = false;
						continue;
					}
					String[] splited = line.split("#");
					File f = new File(splited[0]);
					if (!f.isFile()) {
						System.out.println(f.getPath() + " not exists, omitted");
						continue;
					}
					Map<String, String> typeSdMap = new HashMap<String, String>();
					int i = 1;
					while (i < splited.length) {
						typeSdMap.put(splited[i].trim(), splited[i+1].trim());
						i += 2;
					}
					if (init) {
						addInitNode(splited[0].trim(), typeSdMap);
					} else {
						addTestNode(splited[0].trim(), typeSdMap);
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	public void setupScenario() {
		KGateway gateway = simulation.addGateway(getNextLoc());
		
		sb.append("Initial sensors:\n");
		sb.append(String.format("%19s %14s %6s %14s %14s %10s\n", "ID", "Type", "", "SD",
				"", ""));
		for (Iterator<NodeInfo> iterator = initNodeInfoList.iterator(); iterator.hasNext();) {
			NodeInfo info = iterator.next();
			String path = info.npfPath;
			Map<String, String> typeSdMap = info.typeSdMap;
			KSensorNode sNode = simulation.addSensorNode(path, getNextLoc());
			List<KSensor> sensorList = sNode.getSensorList();
			for (KSensor sensor : sensorList) {
				String sd = typeSdMap.get(sensor.getSensorType());
				if (sd == null) {
					sensor.setPowerOn(false);
				} else {
					sensor.setPowerOn(true);
					sensor.setDescription(sd);
					sb.append(String.format("%19s %14s %6s %14s %14s %10s\n",
							sNode.getNodeId(), sensor.getSensorType(), "", sd, "", ""));
				}
			}
		}
	}

	private TPoint getNextLoc() {
		TPoint point = new TPoint(col * width + 80, row * height + 80);
		col++;
		if (col >= MAXCOL) {
			col = 0;
			row++;
		}
		return point;
	}

	public void runEvaluation(long revertInterval) {
		simulation.startSimulation();
		simulation.pauseSimulation();
		simulation.setRefreshPeriod(refreshPeriod);
		simulation.setTimeTick(timeTick);
		revertPoint = simulation.getSimTime() + revertInterval;
		simulation.setSimTime(revertPoint);
		testNextGroup();
		simulation.resumeSimulation();
	}

	private void testNextGroup() {
		// clear fuzzy rule base
		for (KNode node : simulation.getNodeList()) {
			if (node instanceof KGateway) {
				((KGateway) node).getFuzzyDb().clearFuzzyRuleBase();
			}
		};
		// choose the next sensor node
		int indx = 0;
		NodeInfo info = testNodeInfoList.get(indx);
		testNodeInfoList.remove(indx);
		String npfPath = info.npfPath;
		Map<String, String> typeSdMap = info.typeSdMap;
		KSensorNode sNode = simulation.addSensorNode(npfPath, getNextLoc());
		System.out.println(sNode.getDisplayName() + "added");
		List<KSensor> sensorList = sNode.getSensorList();
		for (KSensor sensor : sensorList) {
			String desiredSD = typeSdMap.get(sensor.getSensorType());
			if (desiredSD == null) {
				sensor.setPowerOn(false);
			} else {
				sensor.setPowerOn(true);
				EvalRecord record = new EvalRecord();
				record.npfPath = npfPath;
				record.nodeId = sNode.getNodeId();
				record.nodeAddr = sNode.getNetAddr();
				record.sensorType = sensor.getSensorType();
				record.desiredSD = desiredSD;
				recordList.add(record);
			}
		}
		sNode.addRankListRecvListener(new RankListRecvListener() {

			@Override
			public void onRankListRecv(KSensor sensor, RankList rankList) {

				recordAccuracy(sensor, rankList);
				doInProgressStat();
				if (testNodeInfoList.size() > 0) {
					simulation.removeNode(sensor.getHostNode());
					col--;
					System.out.println(sensor.getHostNode().getDisplayName() + "removed");
					simulation.setSimTime(revertPoint);	
					testNextGroup();
				} else {
					doFinalStat();
					simulation.stopSimulation();
					System.exit(0);
				}
			}

		});
	}
					
	private void doInProgressStat() {
		EvalRecord record = recordList.get(recordList.size() - 1);
		double accuracy =  record.accuracy;

		System.err.format("%10s   %10s\n", "TestNum", "accuracy");
		System.err.format("%10d   %10.4f\n", simulation.getNodeList().size() - 1, accuracy);
	}

	private void recordAccuracy(KSensor sensor, RankList rankList) {
		EvalRecord record = findRecord(recordList,
				sensor.getHostNode().getDatapath(),
				sensor.getSensorType());
		if (record == null) {
			throw new RuntimeException();
		}
		
		int size = rankList.getList().size();
		double sumNumberOfNonCorrectSensor = 0;
		int numberOfCorrectSensor = 0;
		ListIterator<RankRecord> iter = rankList.getList().listIterator();
		while (iter.hasNext()) {
			int curPos = iter.nextIndex();
			RankRecord rankRecord = iter.next();
			if (rankRecord.getSensorDescription().equals(record.desiredSD)) {
				numberOfCorrectSensor++;
				ListIterator<RankRecord> iter1 = rankList.getList().listIterator();
				while (iter1.nextIndex() < curPos) {
					RankRecord rrc = iter1.next();
					if (!rrc.getSensorDescription().equals(record.desiredSD))
						sumNumberOfNonCorrectSensor++;
				}
			}
		}
		record.accuracy = 1 - (double)sumNumberOfNonCorrectSensor/(double)numberOfCorrectSensor/(double)(size-numberOfCorrectSensor);
		record.rankList = rankList;
	}
	
	protected void doFinalStat() {
		double[] accuracyArr = new double[recordList.size()];
		int i = 0;
		for (EvalRecord record : recordList) {
			accuracyArr[i++] = record.accuracy;
		}
		double mean = calMean(accuracyArr);
		double sd = calDeviation(accuracyArr, mean);
		System.err.format("Mean Accuracy: %f\nStandard Deviation: %f\n\n", mean, sd);
		System.err.println(sb);

		System.err.println("Test sensors:");
		for (EvalRecord record : recordList) {
			System.err.format("nodeID=%s   sensorType=%s   nodeAddr=%s   desiredSD=%s\n   accuracy=%f\n",
					record.nodeId, record.sensorType, record.nodeAddr, record.desiredSD, record.accuracy);
			System.err.println("---------------------Rank list-----------------------");
			System.err.print(record.rankList);
			System.err.println("-----------------------------------------------------\n");
		}
	}
	
	private EvalRecord findRecord(List<EvalRecord> recordList, String datapath, String sensorType) {
		for (EvalRecord evalRecord : recordList) {
			if (evalRecord.npfPath.equals(datapath) && evalRecord.sensorType.equals(sensorType)) {
				return evalRecord;
			}
		}
		return null;
	}
	
	private double calMean(double[] scores) {
		double sum = 0;;
		for (double d : scores) {
			sum += d;
		}
		
		return sum / scores.length;
	}
	
	private double calDeviation(double[] scores, double mean) {
		double sum = 0;
		for (double d : scores) {
			sum += (d - mean) * (d - mean);
		}
		return Math.sqrt(sum / scores.length);
	}
}
